/**
 *	\file AKTTTBasicDrawing.m
 *  
 *  \section desc_sec Description
 *  Description
 *  
 *  \if copyright
 *  	Copyright 2008 Ben Chen. All rights reserved.
 *  \endif
 *  
 *  \section hist_sec Change History:
 *  	-	1/19/08		BC		Created this file
 *  
 *  <hr>
 *  \addtogroup AnimalKingdom
 *  @{
 */

#import <QuartzCore/CoreAnimation.h>

#import "AKImageProvider.h"
#import "AKGeometryProvider.h"
#import "AKBasicInfoProvider.h"

#import "AKDrawing.h"

typedef struct _PieceInfo {
	AKPiece		piece;
	CALayer*	layer;
	CALayer*	reflection;
} PieceInfo;

@interface AKDrawing ()
@property (readwrite, retain) id <AKImageProvider>		imageProvider;
@property (readwrite, retain) id <AKGeometryProvider>	geometryProvider;
@property (readwrite, retain) id <AKBasicInfoProvider>	basicInfoProvider;
//@property (readwrite, retain) id <AKStateMaintainer>	stateMaintainer;
@property (readwrite, retain) NSView*					drawingView;
@end

#define AKDrawingActivateFilterName @"pulseFilter"

@implementation AKDrawing

@synthesize imageProvider, geometryProvider, basicInfoProvider, drawingView;

#pragma mark Helper Function
- (NSRect)rectInViewForRect:(NSRect)aRect {
	NSRect retVal;
	
	NSSize chessboard	= [geometryProvider chessboardSize];
	NSRect viewSize		= [drawingView bounds];
	
	//NSLog(@"viewSize: %f %f", viewSize.size.width, viewSize.size.height);
	
	float radio;
	if (viewSize.size.width/viewSize.size.height > chessboard.width/chessboard.height)
		radio = viewSize.size.height / chessboard.height;
	else
		radio = viewSize.size.width / chessboard.width;
	
	retVal.size.width = aRect.size.width * radio;
	retVal.size.height = aRect.size.height * radio;
	retVal.origin.x = aRect.origin.x * radio;
	retVal.origin.y = aRect.origin.y * radio;
	
	return retVal;
}

- (CGRect)rectForCell:(AKCellIndex)cellIndex {
	NSRect rect			= [geometryProvider rectForCell:cellIndex];
	NSRect rectToDraw	= [self rectInViewForRect:rect];
	
	return CGRectFromNSRect(rectToDraw);
}

#pragma mark Initialization

- (id)initWithImageProvider:(id <AKImageProvider>)anImageProvider 
		   geometryProvider:(id <AKGeometryProvider>)aGeometryProvider
		  basicInfoProvider:(id <AKBasicInfoProvider>)aBasicInfoProvider
			//stateMaintainer:(id <AKStateMaintainer>)aStateMaintainer
				   drawView:(NSView*)aDrawView {
	self = [super init];
	
	if (self) {
		self.imageProvider		= anImageProvider;
		self.geometryProvider	= aGeometryProvider;
		self.basicInfoProvider	= aBasicInfoProvider; 
		//self.stateMaintainer	= aStateMaintainer;
		self.drawingView		= aDrawView;
		
		pieces = [[NSMutableDictionary alloc] init];
		
		// Create a Container Layer to hold all layers
		containerLayer = [CALayer layer];
		
		// Create a Layer for backgound
		background = [CALayer layer];
		background.contents = (id)[imageProvider backgroundImage];
		background.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
		background.bounds = containerLayer.bounds;
		// FIXME: lay out background correctly
		
		// Create a Layer for reflections (if necessary)
		reflectionLayer = [CALayer layer];
		reflectionLayer.name = @"reflectionLayer";
		
		// Create a Layer for special effects
		specialEffectLayer = [CALayer layer];
		specialEffectLayer.name = @"specialEffectLayer";
		
		// Create a Layer for pieces
		piecesLayer = [CALayer layer];
		piecesLayer.name = @"piecesLayer";
		
		// Add all to container layer
		containerLayer.sublayers = [NSArray arrayWithObjects:background, reflectionLayer, specialEffectLayer, piecesLayer, nil];
		
		[drawingView setLayer:containerLayer];
		[drawingView setWantsLayer:YES];
		
		dragAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
		dragAnimation.duration = 3.0f;
		[dragAnimation retain];
	}
	
	return self;
}		

- (void) dealloc {
	[(id)imageProvider release];
	[(id)geometryProvider release];
	//[(id)stateMaintainer release];
	[(id)drawingView release];
	
	[pieces release];
	
	[dragAnimation release];
	
	[super dealloc];
}

#pragma mark Basic Drawing

- (void)removeAllPieces {
	for (NSString *key in pieces) {
		NSData *pieceData = [pieces valueForKey:key];
		PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
		[pieceInfo->layer removeFromSuperlayer];
		[pieceInfo->reflection removeFromSuperlayer];
	}
	
	[pieces removeAllObjects];
}

- (void)addPiece:(AKPiece)piece {
	BOOL retVal = YES;
	
	NSString *pieceKey = AKStringFromPiece(piece);
	NSData *pieceData = [pieces valueForKey:pieceKey];
	
	if (pieceData)
		// same piece cannot be added twice
		retVal = NO;
	
	if (retVal) {
		CGRect frameRect	= [self rectForCell:piece.cell];
		
		// Create a filter for activation animation
		CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
		[filter setDefaults];
		[filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
		[filter setValue:[NSNumber numberWithFloat:0.0] forKey:@"inputIntensity"];
		[filter	setName:AKDrawingActivateFilterName];
		NSArray *filterArray = [NSArray arrayWithObject:filter];
		
		CALayer* newPiece	= [CALayer layer];
		newPiece.delegate	= self;
		newPiece.contents	= (id)[imageProvider imageForPiece:piece];
		newPiece.frame		= frameRect;
		newPiece.name		= pieceKey;	// layer will later be retrieved by name
		newPiece.filters	= filterArray;
		
		[piecesLayer addSublayer:newPiece];
		
		CALayer* reflection = nil;
		if ([(id)imageProvider respondsToSelector:@selector(reflectionForPiece:)]) {
			reflection = [CALayer layer];
			reflection.delegate	= self;
			reflection.contents	= (id)[imageProvider reflectionForPiece:piece];
			reflection.frame	= frameRect;
			reflection.opacity	= 0.2;
			reflection.name		= pieceKey;	// again, reflection is also retrieved by name
			reflection.filters	= filterArray;
			
			[reflectionLayer addSublayer:reflection];
		}
		
		// Save the piece info for looking up later
		PieceInfo pieceInfo;
		pieceInfo.piece			= piece;
		pieceInfo.layer			= newPiece;
		pieceInfo.reflection	= reflection;
		
		NSData *data = [NSData dataWithBytes:&pieceInfo length:sizeof(PieceInfo)];
		[pieces setValue:data forKey:pieceKey];
	}
	
	return;
}

- (void)removePiece:(AKPiece)piece {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSData *pieceData = [pieces valueForKey:pieceKey];
	
	if (pieceData) {
		PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
		
		[pieceInfo->layer removeFromSuperlayer];
		[pieceInfo->reflection removeFromSuperlayer];
		
		[pieces removeObjectForKey:pieceKey];
	}
}

- (void)movePiece:(AKPiece)piece
		   toCell:(AKCellIndex)dstCell {
	NSString *pieceKey = AKStringFromPiece(piece);
	
	NSData *pieceData = [pieces valueForKey:pieceKey];
	
	if (pieceData) {
		CGRect newFrame = [self rectForCell:dstCell];
		
		PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
		
		(pieceInfo->layer).frame		= newFrame;
		(pieceInfo->reflection).frame	= newFrame;
		pieceInfo->piece.cell			= dstCell;
		
		// change the key of the piece data
		[pieceData retain];
		NSString *newKey = AKStringFromPiece(pieceInfo->piece);
		(pieceInfo->layer).name			= newKey;
		(pieceInfo->reflection).name	= newKey;
		
		[pieces removeObjectForKey:pieceKey];
		[pieces setValue:pieceData forKey:pieceKey];
		[pieceData release];
	}
}

#pragma mark Delegate method
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)key {
	id <CAAction> retVal = nil;
	
	if (layer != draggedPiece && layer != draggedReflection) {
		if ([key isEqualToString:@"contents"]) {
			// Apply an basic animation to the filter
			CABasicAnimation* animation	= [CABasicAnimation animationWithKeyPath:key];
			animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
			
			retVal = animation;
		}
	}
	else {
		NSLog(@"actionForlayer!!!");
		CABasicAnimation* animation	= [CABasicAnimation animationWithKeyPath:key];
		animation.duration = 0.0;
	}
	
	return retVal;
}

#pragma mark Interactive Drawing

- (AKCellIndex)cellIndexForPoint:(NSPoint)position {
	AKCellIndex retVal = NSNotFound;
	
	unsigned i;
	for (i = 0; i < basicInfoProvider.numOfCells; ++i) {
		NSRect rectToDraw = [self rectInViewForRect:[geometryProvider rectForCell:i]];
		if (NSPointInRect(position, rectToDraw)) {
			retVal = i;
			break;
		}
	}
	
	return retVal;
}

- (AKPiece)pieceForPoint:(NSPoint)position {
	AKPiece retVal;
	
	CALayer *hitLayer = [piecesLayer hitTest:CGPointFromNSPoint(position)];
	if (hitLayer && hitLayer != piecesLayer) {
		NSData *pieceData = [pieces valueForKey:hitLayer.name];
		
		if (!pieceData)
			[[NSException exceptionWithName:NSInternalInconsistencyException
									 reason:@"AKDrawing: cannot retrieve data from hit layer."
								   userInfo:nil] raise];
		
		PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
		
		retVal = pieceInfo->piece;
	}
	else
		retVal = AKZeroPiece;
	
	return retVal;
}

- (void)activatePiece:(AKPiece)piece {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSData *pieceData = [pieces valueForKey:pieceKey];
	
	if (!(activatedPiece && activatedPiece == pieceData)) {
		if (activatedPiece && activatedPiece != pieceData)
			[self deactivatePiece];
		
		if (pieceData) {
			PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
			
			CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"filters.pulseFilter.inputIntensity"];
			NSNumber *toValue	= [NSNumber numberWithFloat: 1.0];
			animation.fromValue	= [NSNumber numberWithFloat: 0.0];
			animation.toValue	= toValue;
			animation.duration	= 0.25;
			animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
			
			[(pieceInfo->layer) addAnimation:animation forKey:@"activationAnimation"];
			[(pieceInfo->reflection) addAnimation:animation forKey:@"activationAnimation"];
			
			[(pieceInfo->layer) setValue:toValue
							  forKeyPath:@"filters.pulseFilter.inputIntensity"];
			[(pieceInfo->layer) setValue:toValue
								  forKey:@"filters.pulseFilter.inputIntensity"];
			
			activatedPiece = pieceData;
		}
	}
}

- (void)deactivatePiece {
	if (activatedPiece) {
		PieceInfo *pieceInfo = (PieceInfo*)[activatedPiece bytes];
		
		CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"filters.pulseFilter.inputIntensity"];
		NSNumber *toValue	= [NSNumber numberWithFloat: 0.0];
		animation.fromValue	= [NSNumber numberWithFloat: 1.0];
		animation.toValue	= toValue;
		animation.duration	= 0.25;
		animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
		
		[(pieceInfo->layer)			addAnimation:animation forKey:@"deactivationAnimation"];
		[(pieceInfo->reflection)	addAnimation:animation forKey:@"deactivationAnimation"];
		
		[(pieceInfo->layer) setValue:toValue
						  forKeyPath:@"filters.pulseFilter.inputIntensity"];
		[(pieceInfo->layer) setValue:toValue
							  forKey:@"filters.pulseFilter.inputIntensity"];
		
		activatedPiece = nil;
	}
}

- (void)aboutToDragPiece:(AKPiece)piece atPoint:(NSPoint)aPoint {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSData *pieceData = [pieces valueForKey:pieceKey];
	
	if (pieceData) {
		draggedData = pieceData;
		PieceInfo *pieceInfo = (PieceInfo*)[pieceData bytes];
		
		// Create a new layer for dragging
		CALayer *thePiece = pieceInfo->layer;
		if (!draggedPiece)
			draggedPiece = [CALayer layer];
		draggedPiece.contents	= thePiece.contents;
		draggedPiece.frame		= thePiece.frame;
		draggedPiece.opacity	= 0.5;
		[piecesLayer addSublayer:draggedPiece];
		
		if (pieceInfo->reflection != nil) {
			CALayer *theReflection = pieceInfo->reflection;
			if (!draggedReflection)
				draggedReflection = [CALayer layer];
			draggedReflection.contents  = theReflection.contents;
			draggedReflection.frame		= theReflection.frame;
			draggedReflection.opacity	= 0.5;
			[reflectionLayer addSublayer:draggedReflection];
		}
	}
}

- (void)dragPieceAt:(NSPoint)point {	
	CGPoint position = CGPointFromNSPoint(point);
	draggedPiece.position = position;
	//[draggedPiece addAnimation:dragAnimation forKey:@"drag"];
	
	if (draggedReflection) {
		draggedReflection.position = position; 
		//[draggedReflection addAnimation:dragAnimation forKey:@"drag"];
	}
}

- (void)stopDragging {
	PieceInfo *pieceInfo = (PieceInfo*)[draggedData bytes];
	
	draggedPiece.frame		= (pieceInfo->layer).frame;
	draggedPiece.opacity	= 0.0;
	
	if (draggedReflection) {
		draggedReflection.frame = (pieceInfo->reflection).frame;
		draggedPiece.opacity	= 0.0;
	}
	
	draggedData = nil;
}

@end

/** @} */
